package uk.ac.cam.cl.oscar.sift;

import uk.ac.cam.cl.oscar.*;

import java.util.Vector;
import java.awt.image.BufferedImage;
import java.io.*;
import javax.imageio.*;

public class OctavePyramid
{
    private Vector<Octave> octaves; // The pyramid of octaves

    public int getLevels() { return this.octaves.size(); }

	/** Get a single octave */
	public Octave getLevel (int n) { return this.octaves.get(n); }

    /**
     * Builds a pyramid of octaves with a specified source image, number of levels
     * and sigma.
     *
     * Ok, this is going to be quite a slim explanation of this algorithm, but bear
     * with me.
     *
     * @param source The original image
     * @param levels The number of levels (intervals) in each octave
     * @param sigma Sigma
     * @param minSize The smallest dimension we will allow after convolving
     */
    public void makeOctaves(BufferedImage source, int levels, double sigma, int minSize)
    {
        // The octaves are stacked on top of one another, but Vector is more suitable
        octaves = new Vector<Octave>();
        BufferedImage previous = source; // The image after being convolved by the previous octave

        while ((previous != null) && (previous.getWidth() >= minSize) && (previous.getHeight() >= minSize)) {
            Octave oct = new Octave(); // We're adding an octave to the pyramid

            // Build the stack of blurred images.
            // "We must produce s + 3 [where s is the integer number of intervals] images in
            // the stack of blurred images for each octave, so that final extreme detection
            // covers a complete octave" (Lowe, 2004)
            BufferedImage[] images = new BufferedImage[levels + 3];

            BufferedImage prev = previous;
            images[0] = previous;

            double w = sigma;
            // Our k is given by w = 2^(1/s)
            double kTerm = Math.sqrt(Math.pow(Math.pow(2.0, 1.0 / levels),2.0) - 1.0);
            // We must produce s+3 images, where each image is the gaussian convolution of the
            // previous image offset by the current w
            for (int i=1; i<images.length; i++) 
            {
                images[i] = ImageOperations.gaussian2D(prev, 1 + 2*(int)(3.0*w*kTerm), w * kTerm);
                prev = images[i];
                w *= Math.pow(2.0, 1.0 / levels);
            }
            // Add the images to the stack
            oct.addBlurredImages(images);
            

            // Build the Difference-of-Gaussian images
            // Only the first 0xFF (i.e. blue) channel is being used, as a complete difference
            images = new BufferedImage[levels + 2];
            for (int n=0; n<images.length; n++) {
                BufferedImage tmp = new BufferedImage(oct.getBlurredImage(n).getWidth(), oct.getBlurredImage(n).getHeight(), BufferedImage.TYPE_INT_ARGB);
                for (int i=0; i<oct.getBlurredImage(n+1).getWidth(); i++) {
                    for (int j=0; j<oct.getBlurredImage(n+1).getHeight(); j++) {
                    	/* The 0xFF000000 is purely to add alpha to the images, for david's bodgy debugging */
                        tmp.setRGB(i,j, 0xFF000000 + 
                        	ImageUtil.RGBtoMagnitude( Math.abs(
                        		oct.getBlurredImage(n+1).getRGB(i,j) & 0xFFFFFF - 
                        		oct.getBlurredImage(n).getRGB(i,j)  & 0xFFFFFF
                        	) )
                        );
                    }
                }
                images[n] = tmp;     
            }
                       
            oct.addDoGImages(images); // Add the DoG images to the DoG stack

            octaves.addElement(oct); // Add the octave to the pyramid

            // "Once a complete octave has been processed, we resample the Gaussian image
            // that has twice the initial value of sigma (it will be 2 images from the top
            // of the stack)" (Lowe, 2004)
            previous = ImageUtil.ScaleHalf(oct.getBlurredImage(oct.getBlurredImageCount() - 2));

        }
    }
}
